package com.cheche.dn.infrastructure.security;

import com.cheche.dn.domain.admin.Admin;
import com.cheche.dn.domain.user.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import java.util.Base64;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Component("tokenService")
public class TokenService {

    private static final Logger logger = LoggerFactory.getLogger(TokenService.class);
    public static final long USER_TOKEN_TIME_OUT = 30;      // 30 days
    public static final long ADMIN_TOKEN_TIME_OUT = 2;      // 2 hours
    private static final String USER_TOKEN_KEY = "token:user:";
    private static final String ADMIN_TOKEN_KEY = "token:admin:";
    private static final String USER_TAG = "1";
    private static final String ADMIN_TAG = "0";

    @Autowired
    @Qualifier("tokenRedis")
    private RedisTemplate<String, Object> tokenRedis;

    public String generateNewToken(Authentication authentication) {
        Object principal = authentication.getPrincipal();
        String src = UUID.randomUUID().toString();
        if (principal instanceof User) {
            src = ((User) principal).getId() + ":" + src + ":" + USER_TAG;
        } else if (principal instanceof Admin) {
            src = ((Admin) principal).getId() + ":" + src + ":" + ADMIN_TAG;
        }
        return Base64.getEncoder().encodeToString(src.getBytes());
    }

    public void store(Authentication authentication) {
        Object principal = authentication.getPrincipal();
        String key = new String(Base64.getDecoder().decode((String) authentication.getDetails()));
        if (principal instanceof User) {
            // 清除该Id相关的所有Token,控制一个用户同时只能登陆一个设备
            String id = key.split(":")[0];
            clearUserToken(Long.valueOf(id));
            // 设置Token
            tokenRedis.opsForValue().set(userTokenKey(key), authentication, USER_TOKEN_TIME_OUT, TimeUnit.DAYS);
        } else if (principal instanceof Admin) {
            tokenRedis.opsForValue().set(adminTokenKey(key), authentication, ADMIN_TOKEN_TIME_OUT, TimeUnit.HOURS);
        }
    }

    public Authentication retrieve(String token) {
        String key = new String(Base64.getDecoder().decode(token));
        String[] strings = key.split(":");
        if (strings.length != 3) return null;
        Object object = null;
        if (strings[2].equals(USER_TAG)) {
            String userTokenKey = userTokenKey(key);
            if (tokenRedis.hasKey(userTokenKey)) {
                object = tokenRedis.opsForValue().get(userTokenKey);
                tokenRedis.expire(userTokenKey, USER_TOKEN_TIME_OUT, TimeUnit.DAYS);
                logger.debug("Refresh user token {} expire time", token);
            }
        } else if (strings[2].equals(ADMIN_TAG)) {
            String adminTokenKey = adminTokenKey(key);
            if (tokenRedis.hasKey(adminTokenKey)) {
                object = tokenRedis.opsForValue().get(adminTokenKey);
                tokenRedis.expire(key, ADMIN_TOKEN_TIME_OUT, TimeUnit.HOURS);
                logger.debug("Refresh admin token {} expire time", token);
            }
        }
        return (Authentication) object;
    }

    private String userTokenKey(String token) {
        return USER_TOKEN_KEY + token;
    }

    private String adminTokenKey(String token) {
        return ADMIN_TOKEN_KEY + token;
    }

    public void clearUserToken(Long id) {
        Set<String> keys = tokenRedis.keys(userTokenKey(id.toString()) + ":*");
        for (String key : keys) {
            tokenRedis.delete(key);
        }
    }

    public void clearAdminToken(Long id) {
        Set<String> keys = tokenRedis.keys(adminTokenKey(id.toString()) + ":*");
        for (String key : keys) {
            tokenRedis.delete(key);
        }
    }

}
